package net.pantolomin.nayo;

import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketOption;
import java.nio.channels.spi.AbstractSelectableChannel;
import java.util.Map;
import java.util.Map.Entry;

import net.pantolomin.nayo.api.ConnectionEventsListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Represents a connector using NIO as the backing implementation
 * 
 * @param <SC>
 *          the type of selectable channel
 */
@SuppressWarnings("rawtypes")
abstract class NioConnector<SC extends AbstractSelectableChannel> {
	/** The logger */
	private final Logger logger;

	/** The provider */
	private final NioProvider provider;

	/** The host's address */
	private final InetAddress hostAddress;

	/** The port to listen on */
	private final int port;

	/** The listener for incoming data */
	private final ConnectionEventsListener eventsListener;

	/** The channel used in the communication */
	private final SC channel;

	/**
	 * Create the acceptor
	 * 
	 * @param provider
	 *          the provider
	 * @param hostAddress
	 *          the host's address
	 * @param port
	 *          the port
	 * @param socketOptions
	 *          the socket options
	 * @param eventsListener
	 *          the listener for events
	 * @throws IOException
	 *           if the communication channel could not be opened
	 */
	@SuppressWarnings("unchecked")
	protected NioConnector(NioProvider provider, InetAddress hostAddress, int port, Map<SocketOption, Object> socketOptions,
			ConnectionEventsListener eventsListener) throws IOException {
		this.logger = LoggerFactory.getLogger(getClass());
		this.provider = provider;
		this.hostAddress = hostAddress;
		this.port = port;
		this.eventsListener = eventsListener;
		this.channel = initChannel();
		if (socketOptions != null) {
			for (Entry<SocketOption, Object> entry : socketOptions.entrySet()) {
				try {
					setOption(this.channel, entry.getKey(), entry.getValue());
				} catch (UnsupportedOperationException e) {
					getLogger().error("Option unavailable", e);
				} catch (IllegalArgumentException e) {
					getLogger().error("Option value incompatible", e);
				}
			}
		}
	}

	/**
	 * @param channel
	 *          the channel
	 * @param option
	 *          the option
	 * @param value
	 *          the value
	 * @throws IOException
	 *           if an error occurred
	 */
	protected abstract <T> void setOption(SC channel, SocketOption<T> option, T value) throws IOException;

	/**
	 * Initialize the channel
	 * 
	 * @return the channel used in the communication
	 * @throws IOException
	 *           if the communication channel could not be opened
	 */
	protected abstract SC initChannel() throws IOException;

	/**
	 * Start the connector
	 */
	abstract void start();

	/**
	 * Stop the connector
	 */
	public void stop() {
		getProvider().getSelectionLoop().closeChannel(getChannel());
	}

	/**
	 * Close the connector. This method is called only by the "selection thread".
	 * 
	 * @throws IOException
	 *           if an error occurred
	 */
	void close() throws IOException {
		getChannel().close();
	}

	/**
	 * @return the logger
	 */
	protected final Logger getLogger() {
		return this.logger;
	}

	/**
	 * @return the host's address
	 */
	public final InetAddress getHostAddress() {
		return this.hostAddress;
	}

	/**
	 * @return the port
	 */
	public final int getPort() {
		return this.port;
	}

	/**
	 * @return true if the connector is open
	 */
	public final boolean isOpen() {
		return getChannel().isOpen();
	}

	/**
	 * @return the provider
	 */
	final NioProvider getProvider() {
		return this.provider;
	}

	/**
	 * @return the listener for incoming data
	 */
	final ConnectionEventsListener getEventsListener() {
		return this.eventsListener;
	}

	/**
	 * @return the channel used in the communication
	 */
	final SC getChannel() {
		return this.channel;
	}
}